package net.wicp.tams.common.os.pool;

import java.io.BufferedReader;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.Validate;

import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.ConnectionMonitor;
import ch.ethz.ssh2.SCPClient;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;
import lombok.extern.slf4j.Slf4j;
import net.wicp.tams.common.Conf;
import net.wicp.tams.common.Result;
import net.wicp.tams.common.apiext.IOUtil;
import net.wicp.tams.common.apiext.ReflectAssist;
import net.wicp.tams.common.apiext.StringUtil;
import net.wicp.tams.common.apiext.tools.PathEndFilter;
import net.wicp.tams.common.beans.MemoryInfo;
import net.wicp.tams.common.callback.ILineProcessor;
import net.wicp.tams.common.constant.DateFormatCase;
import net.wicp.tams.common.constant.JvmStatus;
import net.wicp.tams.common.constant.dic.YesOrNo;
import net.wicp.tams.common.exception.ExceptAll;
import net.wicp.tams.common.exception.ProjectException;
import net.wicp.tams.common.os.bean.DockContainer;
import net.wicp.tams.common.os.bean.FileBean;
import net.wicp.tams.common.os.constant.CommandCentOs;
import net.wicp.tams.common.os.tools.DockerAssit;
import net.wicp.tams.common.thread.ThreadPool;

/**
 * 提供扩展方法
 * 
 * @author 偏锋书生
 *
 *         2018年7月12日
 */
@Slf4j
public class SSHConnection {

	private final Connection conn;

	private boolean isClosed = false;

	public Connection getConn() {
		return conn;
	}

	public SSHConnection(Connection conn) {
		Validate.notNull(conn, "conn不需需传null值");
		Validate.isTrue(conn.isAuthenticationComplete(), "SSH authentication failed");
		SSHConnectionMonitor connMonitor = new SSHConnectionMonitor();
		conn.addConnectionMonitor(connMonitor);
		this.conn = conn;
	}

	public Session openSession() throws ProjectException {
		Session openSession;
		try {
			openSession = this.conn.openSession();
		} catch (IOException e) {
			log.error("opensession error", e);
			throw new ProjectException(ExceptAll.ssh_session_open, e.getMessage());
		}
		return openSession;
	}

	public SCPClient createSCPClient() throws ProjectException {
		try {
			SCPClient createSCPClient = this.conn.createSCPClient();
			return createSCPClient;
		} catch (IOException e) {
			log.error("opensession error", e);
			throw new ProjectException(ExceptAll.ssh_client_create, e.getMessage());
		}
	}

	public void close() {
		this.isClosed = true;
		conn.close();
	}

	public boolean isConnClose() {
		return this.isClosed;
	}

	/**
	 * 得到进程信息 key: procId value: 除procId的其它信息
	 * 
	 * @param greps
	 * @return
	 */
	public Map<Integer, String> jps(String... greps) {
		String cmdstr = CommandCentOs.jps.getCommand(greps);
		Result jpsResult = this.executeCommand(cmdstr);
		String[] jpsDetails = jpsResult.getMessage().split("\n");
		Map<Integer, String> retmap = new HashMap<>();
		for (String jpsDetail : jpsDetails) {
			int firstblank = jpsDetail.indexOf(" ");
			retmap.put(new Integer(jpsDetail.substring(0, firstblank)), jpsDetail.substring(firstblank + 1));
		}
		return retmap;
	}

	/***
	 * 得到docker容器
	 * 
	 * @param greps
	 * @return
	 */
	public List<DockContainer> dockerps(String... greps) {
		String cmdstr = CommandCentOs.dockerps.getCommand(greps);
		Result jpsResult = this.executeCommand(cmdstr);
		String[] psDetails = jpsResult.getMessage().split("\n");
		List<DockContainer> retlist = new ArrayList<>();
		for (String psDetail : psDetails) {
			String[] psEleAry = psDetail.replaceAll("\\s{2,}", "|").split("\\|");
			if (psEleAry.length < 7) {// 防特殊情况
				log.error("不规则的返回：{}", psDetail);
				continue;
			}
			DockContainer ele = new DockContainer();
			ele.setContainerId(psEleAry[0]);
			ele.setImage(psEleAry[1]);
			ele.setCommand(psEleAry[2]);
			ele.setCreated(psEleAry[3]);
			ele.setStatus(psEleAry[4]);
			ele.setPorts(psEleAry[5]);
			ele.setNames(psEleAry[6]);
			retlist.add(ele);
		}
		return retlist;
	}

	public Result kill(JvmStatus jvmStatus, int procId) {
		Result jpsResult = this.executeCommand(String.format("kill -%s %s", jvmStatus.getValue(), procId));
		return jpsResult;
	}

	public Result killDocker(String containerId) {
		Result jpsResult = this.executeCommand(String.format("docker kill %s", containerId));
		return jpsResult;
	}

	public MemoryInfo freeM() {
		String cmdstr = CommandCentOs.free.getCommand();
		Result jpsResult = this.executeCommand(cmdstr);
		String[] jpsDetails = jpsResult.getMessage().split("\n");
		MemoryInfo returnobj = new MemoryInfo();
		String[] mens = jpsDetails[1].replaceAll("\\s+", "|").replaceAll(",", "").replaceAll("\\[", "")
				.replaceAll("\\]", "").replace("\r", "").split("\\|");
		returnobj.setTotalMemory(Integer.parseInt(mens[1]));
		returnobj.setUsedMemory(Integer.parseInt(mens[2]));
		returnobj.setFreeMemory(Integer.parseInt(mens[3]));
		returnobj.setSharedMemory(Integer.parseInt(mens[4]));
		returnobj.setBuffMemory(Integer.parseInt(mens[5]));
		returnobj.setAvailableMemory(Integer.parseInt(mens[6]));
		String[] swaps = jpsDetails[2].replaceAll("\\s+", "|").replaceAll(",", "").replaceAll("\\[", "")
				.replaceAll("\\]", "").replace("\r", "").split("\\|");
		returnobj.setTotalSwap(Integer.parseInt(swaps[1]));
		returnobj.setUsedSwap(Integer.parseInt(swaps[2]));
		returnobj.setFreeSwap(Integer.parseInt(swaps[3]));
		return returnobj;
	}

	public boolean isSessionClose(Session session) {
		try {
			Object retobj = ReflectAssist.getPrivateField(session, "flag_closed");
			return (Boolean) retobj;
		} catch (Exception e) {// 不般不会发生
			log.error("get flag_closed error", e);
			return true;
		}
	}

	/**
	 * 执行命令并返回结果，可以执行多次
	 * 
	 * @param cmd
	 * @return 执行成功Result为true，并携带返回信息,返回信息可能为null 执行失败Result为false，并携带失败信息
	 *         执行异常Result为false，并携带异常
	 */
	public Result executeCommand(String cmd) {
		return executeCommand(cmd, Conf.getInt("common.os.ssh.cmd.timeout"));
	}

	public Result executeCommand(CommandCentOs cmd, String grepStr, Object... params) {
		return executeCommand(cmd.getCommand(grepStr, params));
	}

	public Result tarX(String filePath, String toDirPath) {
		if (StringUtil.isNull(toDirPath)) {
			int lastIndexOf = filePath.lastIndexOf("/");
			toDirPath = filePath.substring(0, lastIndexOf);
		}
		return this.executeCommand(CommandCentOs.tar, null, new Object[] { filePath, "-C", toDirPath });
	}

	public Result tarX(String filePath) {
		return tarX(filePath, null);
	}

	/***
	 * 添加用户
	 * 
	 * @param userName
	 *            用户名
	 * @param password
	 *            密码
	 * @return
	 */
	public Result addUser(String userName, String password) {
		String cmd = String.format(
				"useradd -m -d /home/%s -s /bin/bash %s -p  $(perl -e 'print crypt($ARGV[0], \"password\")' %s)",
				userName, userName, password);
		Result executeCommand = executeCommand(cmd);
		if (!executeCommand.isSuc()) {
			return executeCommand;
		}
		String changeDate = "chage -M 9999 " + userName;// # Maximum number of days between password change
		Result changeResult = executeCommand(changeDate);
		return changeResult;
	}

	/***
	 * 是否支持docker
	 * 
	 * @return
	 */
	public YesOrNo checkDocker() {
		Result executeCommand = executeCommand(DockerAssit.checkDocker());
		if (!executeCommand.isSuc()) {
			return null;
		}
		if ("1".equals(executeCommand.getMessage())) {
			return YesOrNo.yes;
		} else {
			return YesOrNo.no;
		}
	}

	public List<FileBean> llFile(String parentDir, YesOrNo isFile) {
		Result result = null;
		parentDir = StringUtil.hasNull(parentDir, "");
		if (isFile == null) {
			result = this.executeCommand(CommandCentOs.ls, null, parentDir);
		} else {
			String grepstr = isFile == YesOrNo.yes ? "^-" : "^d";
			result = this.executeCommand(CommandCentOs.ls, grepstr, parentDir);
		}
		if (!result.isSuc()) {
			log.error("查询文件列表失败", result.getMessage());
			return null;// 空为失败
		}
		List<FileBean> returnlist = new ArrayList<>();
		if (StringUtil.isNotNull(result.getMessage())) {
			String[] listArry = result.getMessage().split("\n");
			for (String ele : listArry) {
				String[] fileEleAry = ele.replaceAll("\\s+", "|").split("\\|");
				if (fileEleAry.length < 7) {// 防“保存成功”之类字样
					continue;
				}
				FileBean tempobj = new FileBean();
				tempobj.setPermission(fileEleAry[0]);
				tempobj.setGroup(fileEleAry[2]);
				tempobj.setUser(fileEleAry[3]);
				tempobj.setSize(Long.parseLong(fileEleAry[4]));
				try {
					String str = String.format("%s %s", fileEleAry[5], fileEleAry[6]);
					tempobj.setLastUpdateTime(DateFormatCase.YYYY_MM_DD_hhmmss.getInstanc().parse(str));
				} catch (Exception e) {
					log.error("时间解析出错", e);
				}
				tempobj.setFileName(fileEleAry[fileEleAry.length - 1]);
				returnlist.add(tempobj);
			}
		}
		return returnlist;
	}

	public Result executeCommand(String cmd, int timoutMillis) {
		return executeCommand(cmd, null, timoutMillis, false);
	}

	public Result executeCommand(String cmd, ILineProcessor lineProcessor) {
		return executeCommand(cmd, lineProcessor, Conf.getInt("common.os.ssh.cmd.timeout"), false);
	}

	/**
	 * 执行命令并返回结果，可以执行多次
	 * 
	 * @param cmd
	 * @param lineProcessor
	 *            回调处理行
	 * @return 如果lineProcessor不为null,那么永远返回Result.true
	 */
	public Result executeCommand(String cmd, ILineProcessor lineProcessor, int timoutMillis, boolean needroot) {
		Session session = null;
		try {
			session = this.conn.openSession();
			return executeCommand(session, cmd, timoutMillis, lineProcessor);
		} catch (Exception e) {
			String errmsg = "execute ip:" + this.conn.getHostname() + " cmd:" + cmd;
			log.error(errmsg, e);
			return new Result(ExceptAll.ssh_session_command);
		} finally {
			close(session);
		}
	}

	public Result executeCommand(final Session session, final String cmd, final int timoutMillis,
			final ILineProcessor lineProcessor) throws ProjectException {
		Future<Result> future = ThreadPool.getDefaultPool().submit(new Callable<Result>() {
			public Result call() throws Exception {
				session.execCommand(cmd);
				// 如果客户端需要进行行处理，则直接进行回调
				if (lineProcessor != null) {
					processStream(session.getStdout(), lineProcessor);
				} else {
					// 获取标准输出
					String rst = slurp(session.getStdout());
					if (rst != null) {
						return Result.getSuc(rst);
					}
					// 返回为null代表可能有异常，需要检测标准错误输出，以便记录日志
					Result errResult = tryLogError(session.getStderr(), cmd);
					if (errResult != null) {
						return errResult;
					}
				}
				return Result.getSuc();// 成功了，但没有返回值
			}
		});
		Result rst = null;
		try {
			rst = future.get(timoutMillis, TimeUnit.MILLISECONDS);
			future.cancel(true);
		} catch (TimeoutException e) {
			log.error("exec ip:{} {} timeout:{}", this.conn.getHostname(), cmd, timoutMillis);
			rst = new Result(ExceptAll.project_timeout);
		} catch (Exception e) {
			log.error("exec common error", e);
			throw new ProjectException(ExceptAll.ssh_session_command, e.getMessage());
		}
		return rst;
	}

	private Result tryLogError(InputStream is, String cmd) {
		String errInfo = slurp(is);
		if (errInfo != null) {
			log.error("address " + this.conn.getHostname() + " execute cmd:({}), err:{}", cmd, errInfo);
			return Result.getError(errInfo);
		}
		return null;
	}
	
	public Result scpDir(String localDir, String  remoteTargetDir, String mode,String[] includePaths,String... excludePaths) {
		try {
			SCPClient client = conn.createSCPClient();
			scpDirDo(client,new File(localDir),remoteTargetDir,mode,includePaths,excludePaths);
			return Result.getSuc();
		} catch (IOException e) {
			String errmsg = "scp to " + remoteTargetDir + " localDir=" + localDir + " err";
			log.error(errmsg, e);
			return Result.getError(errmsg);
		}
	}
	
	private void scpDirDo(SCPClient client,File localDir,String remoteTargetDir,String mode,String[] includePaths,String[] excludePaths) throws IOException {
		PathEndFilter pathEndFilter = new PathEndFilter(localDir.getPath(),excludePaths,includePaths);
		File[] listFiles = localDir.listFiles((FilenameFilter)pathEndFilter);
		if(ArrayUtils.isEmpty(listFiles)) {
			return;
		}
		List<File> files=new ArrayList<>();
		List<File> dirs=new ArrayList<>();		
		for (File file : listFiles) {
			if(file.isDirectory()) {
				dirs.add(file);
			}else {
				files.add(file);
			}
		}
				
		String[] localFiles=new String[files.size()];
		int i=0;
		for (File file : files) {
			localFiles[i++]=file.getPath();
		}
		//复制文件
		if(localFiles.length>0) {
			client.put(localFiles, remoteTargetDir, mode);
		}
		for (File dir : dirs) {
			//创建目录
			String targetDir = IOUtil.mergeFolderAndFilePath(remoteTargetDir, dir.getName());
			Result creatDir = executeCommand(CommandCentOs.mkdir,null,targetDir);
			if(!creatDir.isSuc()) {
				throw new RuntimeException("创建目录失败:"+creatDir.getMessage());
			}
			scpDirDo(client,dir,targetDir,mode,null,null);//只支持第一层
		}
	}

	public Result scp(String[] localFiles, String[] remoteFiles, String remoteTargetDirectory, String mode) {
		try {
			SCPClient client = conn.createSCPClient();
			client.put(localFiles, remoteFiles, remoteTargetDirectory, mode);
			return Result.getSuc();
		} catch (Exception e) {
			String msg="scp local=" + Arrays.toString(localFiles) + " to " + remoteTargetDirectory + " remote="
					+ Arrays.toString(remoteFiles) + " err";
			log.error(msg, e);
			return Result.getError(msg);
		}
	}

	/***
	 * Copy a set of local files to a remote directory, uses the specified mode when
	 * creating the file on the remote side.
	 * 
	 * @param localFile
	 * @param remoteTargetDirectory
	 * @param mode
	 * @return
	 */
	public Result scp(String localFile, String remoteFileName, String remoteTargetDirectory, String mode) {
		try {
			SCPClient client = conn.createSCPClient();
			client.put(localFile, remoteFileName, remoteTargetDirectory, mode);
			return Result.getSuc();
		} catch (Exception e) {
			String errmsg = "scp to " + remoteTargetDirectory + " localFile=" + localFile + " err";
			log.error(errmsg, e);
			return Result.getError(errmsg);
		}
	}

	public Result scpToDir(String localFile, String remoteTargetDirectory) {
		return scpToDir(localFile, remoteTargetDirectory, "0744");
	}

	public Result scpToDir(String localFile, String remoteTargetDirectory, String mode) {
		return scp(new String[] { localFile }, null, remoteTargetDirectory, mode);
	}

	public Result scpToFile(String localFile, String remoteFile, String remoteTargetDirectory) {
		return scpToFile(localFile, remoteFile, remoteTargetDirectory, "0744");
	}

	public Result scpToFile(String localFile, String remoteFile, String remoteTargetDirectory, String mode) {
		return scp(new String[] { localFile }, new String[] { remoteFile }, remoteTargetDirectory, "0744");
	}

	private String slurp(InputStream is) {
		final StringBuilder buffer = new StringBuilder();
		ILineProcessor lp = new ILineProcessor() {
			public void process(String line, int lineNum) throws Exception {
				if (lineNum > 1) {
					buffer.append(System.lineSeparator());
				}
				buffer.append(line);
			}

			@Override
			public void finish() {

			}
		};
		processStream(is, lp);
		return buffer.length() > 0 ? buffer.toString() : null;
	}

	private void processStream(InputStream is, ILineProcessor lineProcessor) {
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new InputStreamReader(new StreamGobbler(is)));
			String line = null;
			int lineNum = 1;
			while ((line = reader.readLine()) != null) {
				try {
					lineProcessor.process(line, lineNum);
				} catch (Exception e) {
					log.error("err line:" + line, e);
				}
				lineNum++;
			}
			lineProcessor.finish();
		} catch (IOException e) {
			log.error(e.getMessage(), e);
		} finally {
			close(reader);
		}
	}

	private void close(BufferedReader read) {
		if (read != null) {
			try {
				read.close();
			} catch (IOException e) {
				log.error(e.getMessage(), e);
			}
		}
	}

	private static void close(Session session) {
		if (session != null) {
			try {
				session.close();
			} catch (Exception e) {
				log.error(e.getMessage(), e);
			}
		}
	}

	private class SSHConnectionMonitor implements ConnectionMonitor {
		@Override
		public void connectionLost(Throwable reason) {
			isClosed = true;
		}
	}
}
